import { DOMImplementation, DOMParser, XMLSerializer } from "xmldom";
import { readFileSync } from "../../common/system";
import { stringEqual } from "../../common/util";
import { isElement } from "./util";

export const domImpl: DOMImplementation = new DOMImplementation();

export function readXml(path: string): Document | undefined {
    try {
        const parser = new DOMParser();
        const fileContent = readFileSync(path);
        if (fileContent !== "") {
            return parser.parseFromString(fileContent);
        }
    }
    catch {
        return undefined;
    }
}

export function readXmlContent(content: string): Document | undefined {
    try {
        const parser = new DOMParser();
        return parser.parseFromString(content);
    }
    catch {
        return undefined;
    }
}

export function serializeXmlToString(xml: Document): string {
    let serializer = new XMLSerializer();
    return serializer.serializeToString(xml);
}

const htmlEscapeCharaterToNormalObject: { [key: string]: string } = {
    "lt": "<",
    "gt": ">",
    "nbsp": " ",
    "amp": "&",
    "quot": '"'
};

export function htmlEscapeCharacterToNormal(str: string): string {
    return str.replace(/&(lt|gt|nbsp|amp|quot);/ig, (_, t) => htmlEscapeCharaterToNormalObject[t]);
}

const normalToHtmlEscapeCharaterObject: { [key: string]: string } = {
    "<": "&lt;",
    ">": "&gt;",
    "&": "&amp;",
    ':': "&quot;"
};

export function normalToHtmlEscapeCharater(str: string): string {
    return str.replace(/[<>&"]/g, (_, t) => normalToHtmlEscapeCharaterObject[t]);
}

export function createXmlPropertyNode(dom: Document, name: string, value: string, type: string, context: string): Element {
    const element = dom.createElement("property");
    element.setAttribute("name", name);
    element.setAttribute("value", value);
    element.setAttribute("type", type);
    element.setAttribute("context", context);
    return element;
}

export function createXmlElement(dom: Document, name: string, attrs: { [key: string]: string } = {}): Element {
    const element = dom.createElement(name);
    for (const key in attrs) {
        element.setAttribute(key, attrs[key]);
    }
    return element;
}

export function forEachChildElement(element: Element, callback: (e: Element) => void, thisArg?: any) {
    let func = thisArg ? callback.bind(thisArg) : callback;
    for (let i = 0; i < element.childNodes.length; i++) {
        let node = element.childNodes[i];
        if (isElement(node)) {
            func(node);
        }
    }
};

export function getFirstChildByTagName(element: Element, tagName: string) {
    for (let i = 0; i < element.childNodes.length; i++) {
        const node = element.childNodes[i];
        if (isElement(node) && node.tagName === tagName) {
            return node;
        }
    }
}

export function filterElements(element: Element, tagName: string, callback: (e: Element) => string, thisArg?: any): [string, Element[]][] {
    const elements: [string, Element[]][] = [];
    forEachChildElement(element, e => {
        if (e.tagName !== tagName) {
            return;
        }
        const key = callback(e);
        if (key.length === 0) {
            return;
        }
        const existItem = elements.find(e => stringEqual(e[0], key));
        if (existItem) {
            existItem[1].push(e);
        }
        else {
            elements.push([key, [e]]);
        }
    }, thisArg);
    return elements;
}

export function getElementAttribute<T>(element: Element, name: string, converter: (str?: string) => T | undefined): T | undefined;
export function getElementAttribute(element: Element, name: string): string | undefined;
export function getElementAttribute(element: Element, name: string, converter?: (str?: string) => any): any {
    let value = element.hasAttribute(name) ? (element.getAttribute(name) ?? undefined) : undefined;
    if (value) {
        value = htmlEscapeCharacterToNormal(value);
    }
    if (converter) {
        return converter(value);
    }
    return value;
}

export function getInnerText(element: Element): { text: string, cdata: boolean } {
    let cdata = false;
    let text = "";
    let firstChild = element.firstChild;
    if (firstChild?.nodeType === 3 || firstChild?.nodeType === 4) {
        text = firstChild.textContent ? htmlEscapeCharacterToNormal(firstChild.textContent) : "";
        if (firstChild.nodeType === 4) {
            cdata = true;
        }
    }
    return { text, cdata };
}


